package com.seleniti.enpareja.controller.ui.widget;

import org.ioc.commons.flowcontrol.actioncontroller.ActionController;
import org.ioc.commons.flowcontrol.common.impl.ReceiverAdapter;
import org.ioc.commons.flowcontrol.eventbus.EventBus;
import org.ioc.commons.flowcontrol.logincontroller.LoginController.LoginResponse;
import org.ioc.commons.flowcontrol.messageboxfactory.MessageBoxFactory.ConfirmResponse;
import org.ioc.commons.flowcontrol.operationmanager.OperationManager;
import org.ioc.commons.flowcontrol.operationmanager.impl.OperationHandlerFactory;
import org.ioc.commons.ui.features.HasInfoType.InfoType;
import org.ioc.commons.utils.FormatterLogger;

import com.seleniti.enpareja.controller.common.LogInType;
import com.seleniti.enpareja.controller.common.domain.LoggedUserInfo;
import com.seleniti.enpareja.controller.control.controller.LoginFailedCause;
import com.seleniti.enpareja.controller.control.controller.LoginParams;
import com.seleniti.enpareja.controller.control.place.EnParejaPlace;
import com.seleniti.enpareja.controller.model.integration.database.domain.CoupleType;
import com.seleniti.enpareja.controller.model.integration.database.domain.User;
import com.seleniti.enpareja.controller.model.integration.service.CoupleService;
import com.seleniti.enpareja.controller.model.integration.service.SecurityManagementService;
import com.seleniti.enpareja.controller.model.integration.service.UserService;
import com.seleniti.enpareja.controller.ui.base.EnParejaBasePresenter;
import com.seleniti.enpareja.controller.ui.main.AppShell;
import com.seleniti.enpareja.controller.ui.widget.UserForm.Action;
import com.seleniti.enpareja.controller.ui.widget.UserForm.Event;
import com.seleniti.enpareja.controller.ui.widget.UserForm.Operation;

/**
 * Presenter for the SignUp view
 * 
 * @author Jesús Lunar Pérez
 * 
 */
public class UserFormPresenter extends
		EnParejaBasePresenter<UserForm, UserForm.Event, UserForm.Operation, UserForm.Action> implements
		UserForm.Presenter {

	private static final String USER_REQUIRED_PROPERTIES = "couple";

	private static final FormatterLogger logger = FormatterLogger.getLogger(UserFormPresenter.class);

	private UserService userService;
	private SecurityManagementService securityService;
	private AppShell shell;

	private boolean passwordChanged;

	private User editingUser;

	private CoupleService coupleService;

	private String editWhenInitialized;

	public UserFormPresenter(UserForm display, EventBus<Event> eventbus, OperationManager<Operation> operationManager,
			ActionController<Action> actionController) {
		super(display, eventbus, operationManager, actionController);
	}

	@Override
	public void onInitDisplay() {
		super.onInitDisplay();

		this.userService = this.serviceManager.serviceFactory().userService();
		this.coupleService = this.serviceManager.serviceFactory().coupleService();
		this.securityService = this.serviceManager.serviceFactory().securityManagementService();
		this.shell = clientFactory.getShell();
		logger.entering("onInitDisplay");
		bindComponents();

		if (this.editWhenInitialized != null) {
			display.editUser(editWhenInitialized);
			this.editWhenInitialized = null;
		}

		logger.exiting("onInitDisplay");
	}

	protected void bindComponents() {
		display.operationManager().bindOperationsRunning(OperationHandlerFactory.<Operation> forEnablers(display));
		display.operationManager().bindOperation(Operation.GettingUser,
				OperationHandlerFactory.<Operation> forLoaders(display.gettingUserIndicator()));
		display.operationManager().bindOperation(Operation.SavingUser,
				OperationHandlerFactory.<Operation> forLoaders(display.savingIndicator()));
		display.operationManager().bindOperation(Operation.BreakingUp,
				OperationHandlerFactory.<Operation> forLoaders(display.breakingUpIndicator()));
		display.operationManager().bindOperation(Operation.BreakingUp,
				OperationHandlerFactory.<Operation> forLoaders(display.droppingSingleModeIndicator()));
		display.operationManager().bindOperation(Operation.BreakingUp,
				OperationHandlerFactory.<Operation> forLoaders(display.droppingTestModeIndicator()));
		display.operationManager().bindOperation(Operation.TryingLogin,
				OperationHandlerFactory.<Operation> forLoaders(display.tryingLoginIndicator()));
	}

	@Override
	public void onUserEmailChanged() {
		if (display.userEmailEnabled().isEnabled()) {
			display.checkEmail();
		}
	}

	@Override
	public void onCheckEmail() {
		final String userEmail = display.userEmail().getValue();

		if (userEmail != null && !userEmail.isEmpty() && EmailValidator.get().validate(userEmail)) {
			this.userService.checkIfExists(userEmail).performing(Operation.CheckingEmail, operationManager)
					.caller(this).to(new ReceiverAdapter<Boolean>() {

						@Override
						public void success(Boolean result) {
							if (Boolean.TRUE.equals(result)) {
								String message = null;
								if (userEmail.toLowerCase().endsWith("@gmail.com")) {
									message = "La dirección de correo ya está siendo utilizada. Recuerda que si tienes una cuenta de Google no necesitas registrarte.";
								} else {
									message = "La dirección de correo ya está siendo utilizada ¿Olvidaste tu contraseña?";
								}
								display.userEmailCheckInfo().setText(message);
								display.userEmailCheckInfo().setType(InfoType.ERROR);
							} else {
								display.userEmailCheckInfo()
										.setText(
												"Perfecto, aun no estas registrado como usuario utilizando esta dirección de correo");
								display.userEmailCheckInfo().setType(InfoType.OPERATION_OK);
								display.nickNameFocus();
							}

							checkSaveAvailability();
						}
					});
		} else {
			display.checkFormValidity();
		}

	}

	protected void checkSaveAvailability() {
		boolean saveEnabled = true;

		saveEnabled &= !display.userEmailEnabled().isEnabled()
				|| display.userEmailCheckInfo().getType() == InfoType.OPERATION_OK;
		saveEnabled &= !this.passwordChanged || display.passwordCheckInfo().getType() == InfoType.OPERATION_OK;

		display.saveEnabled().setEnabled(saveEnabled);
	}

	@Override
	public void onPasswordChanged() {
		this.display.password2().setValue(null);
		this.passwordChanged = true;
		if (display.password().getValue().isEmpty()) {
			display.passwordCheckInfo().setType(InfoType.WARNING);
			display.passwordCheckInfo().setText("No puedes dejar la contraseña vacía");
		}
	}

	@Override
	public void onPassword2Changed() {
		String password1 = display.password().getValue();
		String password2 = display.password2().getValue();

		if (!password1.isEmpty()) {
			if (password1.equals(password2)) {
				if (password1.length() >= 5) {
					display.passwordCheckInfo().setType(InfoType.OPERATION_OK);
					display.passwordCheckInfo().setText("Contraseña válida");
				} else {
					display.passwordCheckInfo().setType(InfoType.WARNING);
					display.passwordCheckInfo().setText("Contraseña muy pobre. Utiliza una de al menos 5 caracteres.");
				}
			} else {
				display.passwordCheckInfo().setType(InfoType.ERROR);
				display.passwordCheckInfo().setText("Las contraseñas no coinciden");
			}
		}
	}

	@Override
	public void onSave() {
		if (display.checkFormValidity()) {
			if (this.editingUser == null) {
				User user = new User();

				user.setUserEmail(display.userEmail().getValue());

				this.editingUser = user;
			}

			this.editingUser.setGender(display.gender().getValue());
			this.editingUser.setNickName(display.nickName().getValue());
			if (this.passwordChanged) {
				this.editingUser.setPassword(display.password().getValue());
			}

			logger.info("Saving user {0}...", this.editingUser);

			userService.save(this.editingUser, this.passwordChanged).with(USER_REQUIRED_PROPERTIES).caller(this)
					.performing(Operation.SavingUser, operationManager).to(new ReceiverAdapter<User>() {

						@Override
						public void success(final User user) {
							logger.info("User saved OK. Trying to login? {0}", display.isTryLoginOnSave());
							if (!display.isTryLoginOnSave()) {
								LoggedUserInfo lui = loginController.getLoggedUserInfo();
								display.editUser(user);
								eventbus.fireEvent(Event.UserSaved, display.userEmail().getValue(),
										lui != null ? lui.isLogged() : false);
							} else {
								logger.info("Trying to log in...");
								loginController.tryLogin(display.userEmail().getValue(), display.password().getValue(),
										new LoginParams(LogInType.UserPassword),
										new LoginResponse<LoggedUserInfo, LoginFailedCause>() {

											@Override
											public void onLoginOk(LoggedUserInfo lui) {
												display.editUser(user);
												boolean loggedIn = lui != null ? lui.isLogged() : false;
												logger.info("Logged in? {0}", loggedIn);
												eventbus.fireEvent(Event.UserSaved, display.userEmail().getValue(),
														loggedIn);
											}

											@Override
											public void onLoginFailed(LoginFailedCause loginFailedCause,
													Throwable caught) {
												exceptionController.handleException(caught, loginFailedCause.name());

											}
										}, Operation.TryingLogin, operationManager);
							}
						}
					});
		}
	}

	@Override
	public void onEditUser(User user) {
		this.editingUser = user;
		this.passwordChanged = false;
		display.userEmailEnabled().setEnabled(false);
		display.userEmail().setValue(user.getUserEmail());
		display.nickName().setValue(user.getNickName());
		display.gender().setValue(user.getGender());

		LoggedUserInfo lui = this.loginController.getLoggedUserInfo();
		CoupleType coupleType = lui != null ? lui.getCoupleType() : null;
		boolean gotCouple = user.getCouple() != null && coupleType == CoupleType.USERS_COUPLE;
		boolean gotSingle = user.getCouple() != null && coupleType == CoupleType.SINGLE_USER;
		boolean gotTest = user.getCouple() != null && coupleType == CoupleType.TESTING_MODE_COUPLE;
		String coupleName = gotCouple ? (user.getCouple().getNickName() != null ? user.getCouple().getNickName() : user
				.getCouple().getUserEmail()) : "";

		display.couplePanel().setVisible(gotCouple);
		display.singlePanel().setVisible(gotSingle);
		display.testPanel().setVisible(gotTest);

		display.coupleName().setText(coupleName);

		checkSaveAvailability();
	}

	@Override
	public void onBreakUp() {
		breakUp("¿Estás seguro de que quieres dejar de ser la pareja de {0}?", false);
	}

	@Override
	public void onDropSingleMode() {
		breakUp("¿Estás seguro de que quieres abandonar el modo soltero?", false);
	}

	@Override
	public void onDropTestMode() {
		breakUp(null, true);
	}

	@Override
	public void onEditUser(String userId) {
		if (display.isInitialized()) {
			userService.find(userId).performing(Operation.GettingUser, operationManager).with(USER_REQUIRED_PROPERTIES)
					.caller(this).to(new ReceiverAdapter<User>() {

						@Override
						public void success(User result) {
							display.editUser(result);
						}
					});
		} else {
			this.editWhenInitialized = userId;
		}
	}

	@Override
	public void onDownloadApp() {
		shell.goToMarketPlace();
	}

	private void breakUp(String message, final boolean goBackToMain) {
		if (this.editingUser != null) {
			User user = this.editingUser;
			boolean gotCouple = this.editingUser.getCouple() != null;
			String coupleName = gotCouple ? (user.getCouple().getNickName() != null ? user.getCouple().getNickName()
					: user.getCouple().getUserEmail()) : "";

			ConfirmResponse confirmResponse = new ConfirmResponse() {

				@Override
				public void onUserResponse(boolean userResponse) {
					if (userResponse) {
						coupleService.breakUp(editingUser).performing(Operation.BreakingUp, operationManager)
								.caller(UserFormPresenter.this).to(new ReceiverAdapter<Void>() {

									@Override
									public void success(Void result) {
										display.couplePanel().setVisible(false);
										loginController.refreshLoggedUserInfo();
										shell.nonAssociatedPanelVisibility().setVisible(true);
										shell.firstStepsVisibility().setVisible(true);
										shell.nonAssociatedButtonVisibility().setVisible(
												!shell.firstStepsVisibility().isVisible());

										if (goBackToMain) {
											placeController.goBackOrTo(EnParejaPlace.Main);
										}
									}
								});
					}

				}
			};
			if (message != null) {
				messageBoxFactory.confirm("Desemparejarte", message, confirmResponse, coupleName);
			} else {
				confirmResponse.onUserResponse(true);
			}
		}
	}

}

class EmailValidator {

	private static final String EMAIL_PATTERN = "^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@"
			+ "[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$";

	private static EmailValidator singleton;

	public static EmailValidator get() {
		return singleton == null ? (singleton = new EmailValidator()) : singleton;
	}

	private EmailValidator() {
	}

	/**
	 * Validate hex with regular expression
	 * 
	 * @param text
	 *            hex for validation
	 * @return true valid hex, false invalid hex
	 */
	public boolean validate(final String text) {

		return text.matches(EMAIL_PATTERN);

	}

}
